from sentence import Sentence
import random


class MinesweeperAI:
    def __init__(self, height=8, width=8, mine_count=8):
        # set init width and height
        self.height = height
        self.width = width
        self.mine_count = mine_count
        # trace clicked cells
        self.moves_made = set()
        # trace safe or mine cell
        self.mines = set()
        self.safes = set()
        # known truth sentence
        self.knowledge = []

    def mark_mine(self, cell):
        self.mines.add(cell)
        for sentence in self.knowledge:
            sentence.mark_mine(cell)

    def mark_safe(self, cell):
        self.safes.add(cell)
        for sentence in self.knowledge:
            sentence.mark_safe(cell)

    def nearby_cells(self, cell):
        cells = set()
        for i in range(cell[0] - 1, cell[0] + 2):
            for j in range(cell[1] - 1, cell[1] + 2):
                if(i, j) == cell:
                    continue

                if 0 <= i < self.height and 0 <= j < self.width:
                    cells.add((i, j))

        return cells

    def add_knowledge(self, cell, count):
        self.moves_made.add(cell)
        # mark safe cell
        if cell not in self.safes:
            self.mark_safe(cell)

        # get cells nearby
        nearby = self.nearby_cells(cell)
        nearby -= self.safes | self.moves_made
        new_sentence = Sentence(nearby, count)
        self.knowledge.append(new_sentence)

        new_safes = set()
        new_mines = set()

        for sentence in self.knowledge:
            if len(sentence.cells) == 0:
                self.knowledge.remove(sentence)
            else:
                tmp_new_safes = sentence.known_safes()
                tmp_new_mines = sentence.known_mines()
                if type(tmp_new_safes) is set:
                    new_safes |= tmp_new_safes
                if type(tmp_new_mines) is set:
                    new_mines |= tmp_new_mines
        for safe in new_safes:
            self.mark_safe(safe)
        for mine in new_mines:
            self.mark_mine(mine)
        prev_sentence = new_sentence
        new_inferences = []
        for sentence in self.knowledge:
            if len(sentence.cells) == 0:
                self.knowledge.remove(sentence)
            elif prev_sentence == sentence:
                break
            elif prev_sentence.cells <= sentence.cells:
                inf_cells = sentence.cells - prev_sentence.cells
                inf_count = sentence.count - prev_sentence.count
                new_inferences.append(Sentence(inf_cells, inf_count))
            prev_sentence = sentence
        self.knowledge += new_inferences

    def make_safe_move(self):
        safe_moves = self.safes.copy()
        safe_moves -= self.moves_made
        if len(safe_moves) == 0:
            return None
        return safe_moves.pop()

    def make_random_move(self):
        if len(self.moves_made) == 56:
            return None
        random_move = random.randrange(self.height), random.randrange(self.height)
        not_safe_moves = self.moves_made | self.mines

        while random_move in not_safe_moves:
            random_move = random.randrange(self.height), random.randrange(self.height)
        return random_move

    # 随机找到格子: 是雷概率最小, 且打开后获得信息最多
    def probability_random(self):
        # 为当前所有未开启的格子根据剩余的雷数计算是雷的平均概率
        avg = self.width * self.height - len(self.safes) - len(self.mines) / self.mine_count - len(self.mines)
        cell_mine_probability_dict = dict()

        # 1. 寻找最值得开启的格子: 假如开启一个格子能获得的精确信息量, 假如雷是稀疏的, 同样开启风险下, 获得信息量越大的越值得开
        cell_info_probability_dict = dict()
        unknown_cells = set()
        for w in range(self.width):
            for h in range(self.height):
                cell = (w, h)
                # 如果该cell已经开启则不再考虑开启它后能够获取的信息量, 以及它本身是雷的概率
                if cell in self.mines or cell in self.safes:
                    pass
                else:
                    # 尚未开启的格子
                    unknown_cells.add(cell)
                    # 这个cell本身是雷的总体平均概率
                    cell_mine_probability_dict[cell] = avg
                    # 计算这个cell周围已经开启的格子数量
                    cell_info_probability = len(self.nearby_cells(cell))
                    # 下面计算格子一旦开启可能获得的精准信息量
                    if w == self.width and h == self.height:
                        # 角上的格子, 一旦打开, 获得的雷数量可用于三个格子, 雷数稀疏的情况下, 这样的信息更加精确
                        cell_info_probability_dict[cell] = 3 - cell_info_probability
                    elif w == self.width or h == self.height:
                        # 边上的格子, 一旦打开, 获得雷的数量可用于五个格子
                        cell_info_probability_dict[cell] = 5 - cell_info_probability
                    else:
                        # 中间的格子, 一旦打开, 获得雷的数量可用于周围8个格子
                        cell_info_probability_dict[cell] = 8 - cell_info_probability

        # 2. 寻找开启风险最低的格子: 先为每一个格子计算是雷的最大概率
        # 格子是雷的概率分组, 用于之后的层层筛选
        cell_mine_probability_set = set()
        # 从已有知识中获得格子是雷的概率, 按可能性的最大值标记该格子
        for (cells, count) in self.knowledge:
            new_probability = len(cells) / count
            for cell in cells:
                old_probability = cell_mine_probability_dict.get(cell)
                if new_probability < old_probability:
                    cell_mine_probability_set.add(new_probability)
                else:
                    pass

        # 3. 寻找最值得开启的一组格子: 按照风险由低到高, 获得信息量由高到底的顺序筛选出一组值得开启的格子
        # size_of_cell_mine_probability_set = len(cell_mine_probability_set)
        # for i in range(size_of_cell_mine_probability_set):
        #     bigger_cell_mine_probability = cell_mine_probability_set[size_of_cell_mine_probability_set - i]
        #     for cell in unknown_cells:
        #         if cell_mine_probability_set[cell] == bigger_cell_mine_probability:


